// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using CIP4.Common;

namespace CIP4.RulesEngine
{
    public class ActionType : CIP4.RulesEngine.BaseActionType
    {
        public static ActionOperatorEnum ActionOperatorTypeFromString(string value)
        {
            if (string.Compare(value, "ASSIGN", true) == 0)
                return ActionOperatorEnum.ASSIGN;
            else if (string.Compare(value, "CREATEITEM", true) == 0)
                return ActionOperatorEnum.CREATEITEM;
            else if (string.Compare(value, "REMOVEFIELD", true) == 0)
                return ActionOperatorEnum.REMOVEFIELD;
            else if (string.Compare(value, "RUN", true) == 0)
                return ActionOperatorEnum.RUN;
            else if (string.Compare(value, "ADDMESSAGE", true) == 0)
                return ActionOperatorEnum.ADDMESSAGE;
            else if (string.Compare(value, "ADDITEMMESSAGE", true) == 0)
                return ActionOperatorEnum.ADDITEMMESSAGE;
            else if (string.Compare(value, "ADDPROPERTYMESSAGE", true) == 0)
                return ActionOperatorEnum.ADDPROPERTYMESSAGE;
            else
                throw new ArgumentException(string.Format("{0} is not a valid ActionOperatorTypeEnum value", value));
        }

        private ActionOperatorEnum m_ActionOperator;
        public ActionOperatorEnum ActionOperator
        {
            get
            {
                return m_ActionOperator;
            }
            set
            {
                m_ActionOperator = value;
            }
        }

        private List<CIValueType> m_Parameters;
        public List<CIValueType> Parameters
        {
            get 
            {
                if (m_Parameters == null)
                    m_Parameters = new List<CIValueType>();
                return m_Parameters; 
            }
        }

        public CIValueType Left
        {
            get
            {
                if (Parameters.Count > 0)
                    return Parameters[0];
                return null;
            }
            set
            {
                if (Parameters.Count > 0)
                    Parameters[0] = value;
                else
                    Parameters.Add(value);
            }
        }
        public CIValueType Right
        {
            get
            {
                if (Parameters.Count > 1)
                    return Parameters[1];
                return null;
            }
            set
            {
                if (Parameters.Count == 0)
                    Parameters.Add(new StringAtomType());
                if (Parameters.Count == 1)
                    Parameters.Add(value);
                else
                    Parameters[1] = value;
            }
        }
        public CIValueType Other
        {
            get
            {
                if (Parameters.Count > 2)
                    return Parameters[2];
                return null;
            }
        }
        public ActionType() { }
        public ActionType(AtomType LeftValue, ActionOperatorEnum ActionOperatorValue, AtomType RightValue)
        {

            Left = LeftValue;
            ActionOperator = ActionOperatorValue;
            Right = RightValue;
        }
        public ActionType(CIValueType LeftValue, ActionOperatorEnum ActionOperatorValue)
        {

            Left = LeftValue;
            ActionOperator = ActionOperatorValue;
        }
        public ActionType(ActionOperatorEnum ActionOperatorValue, List<CIValueType> parameters)
        {
            ActionOperator = ActionOperatorValue;
            Parameters.AddRange(parameters);
        }
        public ActionType(XElement cNode, BaseActionType owner)
        {
            ObjectOwner = owner;
            // Leave this here for the time being to catch any that have it as an element - 
            // stupid idea to do it that way in the first place
            XElement originNode = cNode.Element(XName.Get("Origin", RuleXmlNS));
            if (originNode != null)
                Origin = originNode.Value == "FromCurrentType" ? ActionOriginType.FromCurrentType : ActionOriginType.FromPropertyType;
            XAttribute originAtt = cNode.Attribute(XName.Get("Origin"));
            if (originAtt != null)
                Origin = originAtt.Value == "FromCurrentType" ? ActionOriginType.FromCurrentType : ActionOriginType.FromPropertyType;
            XAttribute usageNode = cNode.Attribute(XName.Get("Usage"));
            if (usageNode != null)
                Usage = ActionUsageEnumFromString(usageNode.Value);
            XAttribute operatorAtt = cNode.Attribute("ActionOperator");
            if (operatorAtt != null)
                ActionOperator = ActionOperatorTypeFromString(operatorAtt.Value);

            XElement descriptionNode = cNode.Element(XName.Get("Description", RuleXmlNS));
            if (descriptionNode != null)
                Description = descriptionNode.Value;
            foreach (XElement paramaterNode in cNode.Elements())
                if (!(paramaterNode.Name == XName.Get("FormalParameters", RuleXmlNS) ||
                    paramaterNode.Name == XName.Get("Description", RuleXmlNS)))
                Parameters.Add(CIValueType.Initialize(paramaterNode));
            InitializeParameters(cNode);
        }
        public override void Execute(IDataPacketType data)
        {
            if (data.PacketStats != null)
                data.PacketStats.ActionOperatorsCount[(int)ActionOperator] += 1;
            switch (ActionOperator)
            {
                case ActionOperatorEnum.ASSIGN:
                    ((AtomType)Left).Assign(Right, data);
                    return;
                case ActionOperatorEnum.REMOVEFIELD:
                    ((AtomType)Left).Remove(data);
                    return;
                case ActionOperatorEnum.ADDMESSAGE:
                    if (Parameters.Count < 3)
                        throw new ArgumentException(string.Format("{0} requires at least three parameters", this.ToString()));
                    string messageId = System.Convert.ToString(Parameters[0].Evaluate(data));
                    string organization = System.Convert.ToString(Parameters[1].Evaluate(data));
                    string severity = System.Convert.ToString(Parameters[2].Evaluate(data));
                    string message = Parameters.Count > 3 ? System.Convert.ToString(Parameters[3].Evaluate(data)) : string.Empty;
                    string parameter1 = Parameters.Count > 4 ? System.Convert.ToString(Parameters[4].Evaluate(data)) : string.Empty;
                    string parameter2 = Parameters.Count > 5 ? System.Convert.ToString(Parameters[5].Evaluate(data)) : string.Empty;
                    string parameter3 = Parameters.Count > 6 ? System.Convert.ToString(Parameters[6].Evaluate(data)) : string.Empty;
                    data.Operations.ADDMESSAGE(messageId, organization, severity, message, parameter1, parameter2, parameter3);
                    return;
                case ActionOperatorEnum.ADDITEMMESSAGE:
                    if (Parameters.Count != 2)
                        throw new ArgumentException(string.Format("{0} requires two parameters", this.ToString()));
                    data.Operations.ADDITEMMESSAGE(System.Convert.ToString(Left.Evaluate(data)), System.Convert.ToString(Right.Evaluate(data)));
                    return;
                case ActionOperatorEnum.ADDPROPERTYMESSAGE:
                    if (Parameters.Count != 3)
                        throw new ArgumentException(string.Format("{0} requires three parameters", this.ToString()));
                    data.Operations.ADDPROPERTYMESSAGE(
                        System.Convert.ToString(Left.Evaluate(data)), 
                        System.Convert.ToString(Right.Evaluate(data)), 
                        System.Convert.ToString(Parameters[2].Evaluate(data)));
                    return;
                default:
                    throw new ApplicationException("unexpected value operator type encountered: working on " + ToString());
            }
        }
        public override bool Check(IDataPacketType data)
        {
            bool result = true;
            if (Left == null)
                data.Logger(MajorEnum.Error, MinorEnum.ExpressionStructure, "The Left operand is missing", new object[] { ToString() });
            else
            {
                if ((Left is ExpressionType || ((AtomType)Left).AtomSource == AtomSourceEnum.literal) &&
                    !(ActionOperator == ActionOperatorEnum.RUN || 
                    ActionOperator == ActionOperatorEnum.ADDPROPERTYMESSAGE ||
                    ActionOperator == ActionOperatorEnum.ADDITEMMESSAGE ||
                    ActionOperator == ActionOperatorEnum.ADDMESSAGE))
                {
                    data.Logger(MajorEnum.Error, MinorEnum.ExpressionStructure, "The Left operand of a {0} cannot be a literal or an expression", new object[] { ActionOperator.ToString(), ToString() });
                    result = false;
                }
            }
            foreach (CIValueType parameter in Parameters)
                parameter.Check(data);
            switch (ActionOperator)
            {
                case ActionOperatorEnum.ASSIGN:
                    break;
                case ActionOperatorEnum.REMOVEFIELD:
                    if (Right != null)
                    {
                        data.Logger(MajorEnum.Error, MinorEnum.ExpressionStructure, "Remove field does not allow a Right operand", new object[] { ToString() });
                        result = false;
                    }
                    break;
                case ActionOperatorEnum.ADDMESSAGE:
                    if (Left == null)
                    {
                        data.Logger(MajorEnum.Error, MinorEnum.ExpressionStructure, "The organization parameter is missing - it should contain a severity value 'Error', 'Warning' or 'Informational'", new object[] { ToString() });
                        return false;
                    }
                    if (Right == null)
                    {
                        data.Logger(MajorEnum.Error, MinorEnum.ExpressionStructure, "The message id parameter is missing - it should contain a message", new object[] { ToString() });
                        return false;
                    }
                    if (Other == null)
                    {
                        data.Logger(MajorEnum.Error, MinorEnum.ExpressionStructure, "The severity parameter is missing - it should contain a message", new object[] { ToString() });
                        return false;
                    }
                    if (Parameters.Count > 7)
                    {
                        data.Logger(MajorEnum.Error, MinorEnum.ExpressionStructure, "There are too many parameters", new object[] { ToString() });
                        return false;
                    }
                    break;
                case ActionOperatorEnum.ADDITEMMESSAGE:
                    if (Left == null)
                    {
                        data.Logger(MajorEnum.Error, MinorEnum.ExpressionStructure, "The severity parameter is missing - it should contain a severity value 'Error', 'Warning' or 'Informational'", new object[] { ToString() });
                        return false;
                    }
                    if (Right == null)
                    {
                        data.Logger(MajorEnum.Error, MinorEnum.ExpressionStructure, "The message parameter is missing - it should contain a message", new object[] { ToString() });
                        return false;
                    }
                    break;
                case ActionOperatorEnum.ADDPROPERTYMESSAGE:
                    if (Left == null)
                    {
                        data.Logger(MajorEnum.Error, MinorEnum.ExpressionStructure, "The severity parameter is missing - it should contain a severity value 'Error', 'Warning' or 'Informational'", new object[] { ToString() });
                        return false;
                    }
                    if (Right == null)
                    {
                        data.Logger(MajorEnum.Error, MinorEnum.ExpressionStructure, "The message parameter is missing - it should contain a message", new object[] { ToString() });
                        return false;
                    }
                    if (Other == null)
                    {
                        data.Logger(MajorEnum.Error, MinorEnum.ExpressionStructure, "The property Name parameter is missing - it should contain a message", new object[] { ToString() });
                        return false;
                    }
                    break;
                default:
                    data.Logger(MajorEnum.Error, MinorEnum.ExpressionStructure, ActionOperator + " is not a recognized operator", new object[] { ToString() });
                    result = false;
                    break;
            }
            return result;
        }
        public override void XmlFor(XmlWriter xwrtr, bool writeXmlNamespace)
        {
            xwrtr.WriteStartElement("Action");
            if (writeXmlNamespace)
                xwrtr.WriteAttributeString("xmlns", RuleXmlNS);
            xwrtr.WriteAttributeString("ActionOperator", ActionOperator.ToString());
            if (Usage.HasValue && Usage != ActionUsageEnum.None)
                xwrtr.WriteAttributeString("Usage", Usage.ToString());
            if (Origin.HasValue)
                xwrtr.WriteAttributeString("Origin", Origin.ToString());
            if (!string.IsNullOrEmpty(Description))
                xwrtr.WriteElementString("Description", Description);
            XmlForFormalParameters(xwrtr);
            foreach (CIValueType parameter in Parameters)
                parameter.XmlFor(xwrtr, false);
            xwrtr.WriteEndElement();
        }
        /// <summary>
        /// For an explanation of how to use this see the comment on ConditionActionSequence.ResolveName
        /// </summary>
        /// <param name="qualifiedName"></param>
        public override void ResolveName(string qualifiedName)
        {
            foreach (CIValueType parameter in Parameters)
                parameter.ResolveName(qualifiedName);
        }

        static private void InitializeOperatorLabels()
        {
            m_ActionOperatorLabels.Add(ActionOperatorEnum.ASSIGN, "gets");
            m_ActionOperatorLabels.Add(ActionOperatorEnum.REMOVEFIELD, "remove field");
            m_ActionOperatorLabels.Add(ActionOperatorEnum.CREATEITEM, "create item");
            m_ActionOperatorLabels.Add(ActionOperatorEnum.RUN, "run");

            m_ActionOperatorAtomLabels.Add(ActionOperatorEnum.ASSIGN, new string[] { "destination", "source value", "not used" });
            m_ActionOperatorAtomLabels.Add(ActionOperatorEnum.REMOVEFIELD, new string[] { "target", "not used", "not used" });
            m_ActionOperatorAtomLabels.Add(ActionOperatorEnum.CREATEITEM, new string[] { "item type", "not used", "not used" });
            m_ActionOperatorAtomLabels.Add(ActionOperatorEnum.RUN, new string[] { "sub name", "script", "not used" });
        }
        static Dictionary<ActionOperatorEnum, string> m_ActionOperatorLabels = new Dictionary<ActionOperatorEnum, string>();
        public static Dictionary<ActionOperatorEnum, string> ActionOperatorLabels
        {
            get
            {
                if (m_ActionOperatorLabels.Count == 0)
                    InitializeOperatorLabels();
                return m_ActionOperatorLabels;
            }
        }
        static Dictionary<ActionOperatorEnum, string[]> m_ActionOperatorAtomLabels = new Dictionary<ActionOperatorEnum, string[]>();
        public static Dictionary<ActionOperatorEnum, string[]> ActionOperatorAtomLabels
        {
            get
            {
                if (m_ActionOperatorAtomLabels.Count == 0)
                    InitializeOperatorLabels();
                return m_ActionOperatorAtomLabels;
            }
        }
        public override BaseActionType Copy()
        {
            return new ActionType(XElement.Parse(XmlFor()), null);
        }
        public override string ToString(string indent)
        {
            string comment = "";
            if (!string.IsNullOrEmpty(Description))
                comment += indent + "' " + Description + Environment.NewLine;
            string leftOp = Parameters.Count > 0 ? Parameters[0].ToString() : "InvalidParameter";
            string rightOp = Parameters.Count > 1 ? Parameters[1].ToString(): "InvalidParameter";
            string otherOp = Parameters.Count > 2 ? Parameters[2].ToString() : "InvalidParameter";
            switch (ActionOperator)
            {
                case ActionOperatorEnum.ASSIGN:
                    return comment + indent + leftOp + " = " + rightOp;
                case ActionOperatorEnum.CREATEITEM:
                    return comment + indent + "Call CreateItem(" + leftOp + ")";
                case ActionOperatorEnum.REMOVEFIELD:
                    return comment + indent + "Call RemoveField(" + leftOp + ")";
                case ActionOperatorEnum.RUN:
                    return comment + leftOp;
                case ActionOperatorEnum.ADDMESSAGE:
                    string text = comment + indent + "Call ADDMESSAGE(" + leftOp + ", " + rightOp + ", " + otherOp;
                    for (int i = 3; i < Parameters.Count; i++)
                        text += ", " + Parameters[i].ToString();
                    text += ")";
                    return text;
                case ActionOperatorEnum.ADDITEMMESSAGE:
                    return comment + indent + "Call ADDITEMMESSAGE(" + leftOp + ", " + rightOp + ")";
                case ActionOperatorEnum.ADDPROPERTYMESSAGE:
                    return comment + indent + "Call ADDPROPERTYMESSAGE(" + leftOp + ", " + rightOp + ", " + otherOp + ")";
                default:
                    return comment + indent + "InvalidAction";
            }
        }
        public override string ToString()
        {
            return ToString("");
        }
    }
}
